//
//  DebugClient.m
//  jskick
//
//  Created by Andrew Barry on 17/02/09.
//  Copyright 2009 Barry Computer Services. All rights reserved.
//

#import "DebugClient.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "jsruntime.h"

extern NSDictionary *gInitBreakpoints;
static NSDictionary *gNextBreakpoints;

bool isDebugging;
int32_t i32SocketFD;
static DebugClient *gClient;

static pthread_mutex_t mutex;
static pthread_cond_t condition;
static bool isSuspended;
int stepMode;
jscontext *debugContext;

BOOL fullRead(int sock, void *buf, int len)
{
	unsigned char *ptr = buf;
	while (len > 0) {
		int r = read(sock, ptr, len);
		if (r <= 0)
			return NO;
		ptr += r;
		len -= r;
	}
	return YES;
}

void sendPacket(NSDictionary *dict) {
//	NSData *packet = [NSPropertyListSerialization dataFromPropertyList:dict format: NSPropertyListBinaryFormat_v1_0 errorDescription:nil];
	NSData *packet = [NSPropertyListSerialization dataFromPropertyList:dict format: NSPropertyListXMLFormat_v1_0 errorDescription:nil];
	int32_t packLen = [packet length];
	write(i32SocketFD, &packLen, 4);
	write(i32SocketFD, [packet bytes], [packet length]);
}

void sendCommand(NSString *action) {
	NSMutableDictionary *dict = [NSMutableDictionary new];
	[dict setObject:action forKey:@"action"];
	sendPacket(dict);
}

static void waitForRelease() {
    pthread_mutex_lock(&mutex);
	
    while(isSuspended) {
        pthread_cond_wait(&condition, &mutex);
    }
	
    pthread_mutex_unlock(&mutex);
	
	if (gNextBreakpoints) {
		resetBreakpoints(debugContext, gNextBreakpoints);
		[gNextBreakpoints release];
	}
	
}

void connectDebugServer(jscontext *cx) {
	debugContext = cx;
	
	pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&condition, NULL);

	NSString *debugPath = [[NSBundle mainBundle] pathForResource:@"debug" ofType:@"plist"];
	if (debugPath == nil) {
		return;
	}
	
	NSDictionary *debugPacket = [NSDictionary dictionaryWithContentsOfFile:debugPath];
	
    i32SocketFD = socket(PF_INET, SOCK_STREAM, 0);
	
    if(-1 == i32SocketFD)
    {
		printf("cannot create socket\n");
		return;
    }
	
    struct sockaddr_in stSockAddr;
    bzero(&stSockAddr, sizeof(stSockAddr));
	
    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    int32_t i32Res = inet_pton(AF_INET, "127.0.0.1",
							   (void *) &stSockAddr.sin_addr);
	
    if(0 > i32Res)
    {
		printf("invalid address\n");
		return;
    }
    else if(0 == i32Res)
    {
		printf("invalid address\n");
		return;
    }
	
    if(-1 == connect(i32SocketFD,(struct sockaddr*) &stSockAddr, sizeof(stSockAddr)))
    {
		printf("connect failed\n");
		close(i32SocketFD);
		return;
    }
	
	NSString *err = nil;
	NSData *packet = [NSPropertyListSerialization dataFromPropertyList:debugPacket format: NSPropertyListBinaryFormat_v1_0 errorDescription:&err];
	int32_t packLen = [packet length];
	write(i32SocketFD, &packLen, 4);
	write(i32SocketFD, [packet bytes], [packet length]);
	
	bool accepted = false;
	
	if (fullRead(i32SocketFD, &packLen, 4)) {
		char *packet = malloc(packLen);
		if (fullRead(i32SocketFD, packet, packLen))
		{
			NSData *data = [NSData dataWithBytes: packet length: packLen];
			NSString *err = nil;
			NSPropertyListFormat fmt;
			NSDictionary *debugPacket = [NSPropertyListSerialization propertyListFromData: data mutabilityOption: NSPropertyListImmutable format: &fmt errorDescription: &err];

			NSString *action = [debugPacket objectForKey:@"action"];
			if ([action isEqualToString:@"accept"]) {
				accepted = true;
				NSDictionary *breakpoints = [debugPacket objectForKey: @"breakpoints"];
				if (breakpoints) {
					gInitBreakpoints = [breakpoints retain];
				}
			}
		}
		
		free(packet);
	}
	
	if (accepted) {
		gClient = [[DebugClient alloc] init];
		isDebugging = true;
		[NSThread detachNewThreadSelector:@selector(listen:) toTarget:gClient withObject:nil];
	} else {
		shutdown(i32SocketFD, 2);
	
		close(i32SocketFD);
	}
}

void fatalError(NSString *message) {
	if (isDebugging) {
		isSuspended = true;
		NSMutableDictionary *dict = [NSMutableDictionary new];
		[dict setObject:@"fatal" forKey:@"action"];
		[dict setObject:message forKey:@"message"];
		sendPacket(dict);
		waitForRelease();
	}
	exit(-1);
}

void debugMessage(NSString *message) {
	if (isDebugging) {
		NSMutableDictionary *dict = [NSMutableDictionary new];
		[dict setObject:@"debugStr" forKey:@"action"];
		[dict setObject:message forKey:@"message"];
		sendPacket(dict);
	}
}

void debugger(NSMutableArray *stack, jsvalue filename, int lineno) {
	if (isDebugging) {
		isSuspended = true;
		NSMutableDictionary *dict = [NSMutableDictionary new];
		[dict setObject:@"debugger" forKey:@"action"];
		if (filename) {
			jsstring *strfile = JSVAL_TO_STRING(filename);
			[dict setObject:[NSString stringWithCharacters:strfile->chars length:strfile->length] forKey:@"filename"];
			[dict setObject:[NSNumber numberWithInt:lineno] forKey:@"lineno"];
		}
		if (stack) {
			[dict setObject:stack forKey:@"stack"];
		}
		sendPacket(dict);
		waitForRelease();
	}
}

void debugError(NSMutableArray *stack, const char *message, jsvalue filename, int lineno) {
	if (isDebugging) {
		isSuspended = true;
		NSMutableDictionary *dict = [NSMutableDictionary new];
		[dict setObject:@"debugger" forKey:@"action"];
		[dict setObject:[NSString stringWithCString:message] forKey:@"message"];
		if (filename) {
			[dict setObject:[NSString stringWithCString:filename] forKey:@"filename"];
			[dict setObject:[NSNumber numberWithInt:lineno] forKey:@"lineno"];
		}
		if (stack) {
			[dict setObject:stack forKey:@"stack"];
		}
		sendPacket(dict);
		waitForRelease();
	}
}

@implementation DebugClient
-(void) bringToFront:(id) param {
	[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}

-(void) listen: (id) param {
	int32_t packLen;
	while (fullRead(i32SocketFD, &packLen, 4)) {
		char *packet = malloc(packLen);
		if (fullRead(i32SocketFD, packet, packLen))
		{
			NSData *data = [NSData dataWithBytes: packet length: packLen];
			NSString *err = nil;
			NSPropertyListFormat fmt;
			NSDictionary *debugPacket = [NSPropertyListSerialization propertyListFromData: data mutabilityOption: NSPropertyListImmutable format: &fmt errorDescription: &err];
			
			NSString *action = [debugPacket objectForKey:@"action"];
			if ([action isEqualToString:@"kill"]) {
				exit(1);
			} else if ([action isEqualToString:@"continue"]) {
				gNextBreakpoints = [[debugPacket objectForKey:@"breakpoints"] retain];
				pthread_mutex_lock(&mutex);
				isSuspended = false;
				stepMode = 0;
				pthread_cond_signal(&condition);
				pthread_mutex_unlock(&mutex);
			} else if ([action isEqualToString:@"stepInto"]) {
				gNextBreakpoints = [[debugPacket objectForKey:@"breakpoints"] retain];
				pthread_mutex_lock(&mutex);
				isSuspended = false;
				stepMode = 1;
				pthread_cond_signal(&condition);
				pthread_mutex_unlock(&mutex);
			} else if ([action isEqualToString:@"stepOver"]) {
				gNextBreakpoints = [[debugPacket objectForKey:@"breakpoints"] retain];
				pthread_mutex_lock(&mutex);
				isSuspended = false;
				stepMode = 2;
				pthread_cond_signal(&condition);
				pthread_mutex_unlock(&mutex);
			} else if ([action isEqualToString:@"stepOut"]) {
				gNextBreakpoints = [[debugPacket objectForKey:@"breakpoints"] retain];
				pthread_mutex_lock(&mutex);
				isSuspended = false;
				stepMode = 3;
				pthread_cond_signal(&condition);
				pthread_mutex_unlock(&mutex);
			} else if ([action isEqualToString:@"front"]) {
				[self performSelectorOnMainThread:@selector(bringToFront:) withObject:self waitUntilDone:NO];
			}
		}
		
		free(packet);
	}
	exit(1);
}
@end
